﻿using System;
using System.Data;
using NHibernate;
using NHibernate.Transaction;

namespace HashFoo.Nhib
{
    /// <summary>
    /// A single transaction per sessoin object.  If a transaction already exists, no new transaction is created.
    /// So, if the caller does not own the transaction, when commit is called, it is ignored until the owner calls commit.
    /// If rollback is called, the transaction is rolled back.
    /// </summary>
    public class SessionTransaction : IDisposable
    {
        readonly ISession _session;
        private bool _committed;

        public SessionTransaction(ISession session, IsolationLevel? isolationLevel = null)
        {
            _session = session;

            if (_session.Transaction == null || _session.Transaction.IsActive == false)
            {
                Transaction = isolationLevel.HasValue ? _session.BeginTransaction(isolationLevel.Value) : _session.BeginTransaction();
                Owned = true;
            }
            else
            {
                // This is a bit of a hack, but ITransaction doesn't expose the isolation level.
                // There is only one implementation of ITransaction in Nhibernate, though, so we should be safe.
                if (isolationLevel != null && ((AdoTransaction)_session.Transaction).IsolationLevel != isolationLevel.Value)
                    throw new ArgumentException(@"Invalid isolation level.  A trasaction is in progress with a different isolation level.", "isolationLevel");

                Transaction = _session.Transaction;
                Owned = false;
            }
        }

        /// <summary>
        /// True if the wrapped transaction is owned
        /// </summary>
        public bool Owned { get; private set; }

        /// <summary>
        /// The underlying nhibernate transaction.  Careful with what you do with it.
        /// </summary>
        public ITransaction Transaction { get; private set; }

        /// <summary>
        /// Commit the transaction, if it is owned.
        /// </summary>
        public void Commit()
        {
            _committed = true;
            if (!Owned)
            {
                _session.Flush();
                return;
            }

            if (Transaction.IsActive == false)
                throw new TransactionException("The transaction being committed is not longer active.");

            Transaction.Commit();
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            if (Transaction == null) return;
            if (_committed || !Transaction.IsActive) return;

            Transaction.Rollback();
            Transaction.Dispose();
            Transaction = null;
        }
    }
}